package client

import (
	"cmp"
	"context"
	"encoding/json/jsontext"
	"encoding/json/v2"
	"fmt"
	"gitee.com/kaylee595/gkaylee/kerr"
	"github.com/imroc/req/v3"
	"net/http"
	url2 "net/url"
	"strconv"
	"time"
)

type Client struct {
	httpClient *req.Client
	host       string
}

func NewClient(httpClient *req.Client, host string) *Client {
	return &Client{
		httpClient: cmp.Or(httpClient, req.DefaultClient()),
		host:       host,
	}
}

func (c *Client) GetHost() string {
	return c.host
}

type Proxy url2.URL

func (p *Proxy) MarshalJSONTo(encoder *jsontext.Encoder) error {
	// 写入host, port, username, password
	t := (*url2.URL)(p)
	err := encoder.WriteToken(jsontext.BeginObject)
	if err != nil {
		return kerr.WrapTraceStack(err)
	}
	{
		err = encoder.WriteToken(jsontext.String("host"))
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
		err = encoder.WriteToken(jsontext.String(t.Hostname()))
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
	}
	{
		err = encoder.WriteToken(jsontext.String("port"))
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
		port, err := strconv.ParseUint(t.Port(), 10, 16)
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
		err = encoder.WriteToken(jsontext.Uint(port))
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
	}
	if t.User != nil {
		err = encoder.WriteToken(jsontext.String("username"))
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
		err = encoder.WriteToken(jsontext.String(t.User.Username()))
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
		password, ok := t.User.Password()
		if ok {
			err = encoder.WriteToken(jsontext.String("password"))
			if err != nil {
				return kerr.WrapTraceStack(err)
			}
			err = encoder.WriteToken(jsontext.String(password))
			if err != nil {
				return kerr.WrapTraceStack(err)
			}
		}
	}
	err = encoder.WriteToken(jsontext.EndObject)
	if err != nil {
		return kerr.WrapTraceStack(err)
	}
	return nil
}

type ReqToken struct {
	Url   string `json:"url"`
	Proxy *Proxy `json:"proxy"`
}

type Cookies []*http.Cookie

func (c *Cookies) UnmarshalJSONFrom(decoder *jsontext.Decoder) error {
	token, err := decoder.ReadToken()
	if err != nil {
		return kerr.WrapTraceStack(err)
	}
	if token.Kind() != '[' {
		return kerr.WrapTraceStack(fmt.Errorf("cookies must be array, path: %s", decoder.StackPointer()))
	}
	for {
		token, err = decoder.ReadToken()
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
		if token.Kind() == ']' {
			break
		} else if token.Kind() != '{' {
			return kerr.WrapTraceStack(
				fmt.Errorf("must be object, path: %s, got token: %s", decoder.StackPointer(), token),
			)
		}
		cc := &http.Cookie{}
		for {
			keyToken, err := decoder.ReadToken()
			if err != nil {
				return kerr.WrapTraceStack(err)
			}
			if keyToken.Kind() == '}' {
				break
			}
			key := keyToken.String()
			valueToken, err := decoder.ReadValue()
			if err != nil {
				return kerr.WrapTraceStack(err)
			}
			kind := valueToken.Kind()
			switch key {
			case "name":
				if kind != '"' {
					return kerr.WrapTraceStack(
						fmt.Errorf("must be a string, path: %s, got value: %s",
							decoder.StackPointer(), valueToken.String()),
					)
				}
				var v string
				err = json.Unmarshal(valueToken, &v)
				if err != nil {
					return kerr.WrapTraceStack(
						fmt.Errorf("%w, path: %s, got value: %s",
							err, decoder.StackPointer(), valueToken.String()),
					)
				}
				cc.Name = v
			case "value":
				if kind != '"' {
					return kerr.WrapTraceStack(
						fmt.Errorf("must be a string, path: %s, got value: %s",
							decoder.StackPointer(), valueToken.String()),
					)
				}
				var v string
				err = json.Unmarshal(valueToken, &v)
				if err != nil {
					return kerr.WrapTraceStack(
						fmt.Errorf("%w, path: %s, got value: %s",
							err, decoder.StackPointer(), valueToken.String()),
					)
				}
				cc.Value = v
			case "domain":
				if kind != '"' {
					return kerr.WrapTraceStack(
						fmt.Errorf("must be a string, path: %s, got value: %s",
							decoder.StackPointer(), valueToken.String()),
					)
				}
				var v string
				err = json.Unmarshal(valueToken, &v)
				if err != nil {
					return kerr.WrapTraceStack(
						fmt.Errorf("%w, path: %s, got value: %s",
							err, decoder.StackPointer(), valueToken.String()),
					)
				}
				cc.Domain = v
			case "path":
				if kind != '"' {
					return kerr.WrapTraceStack(
						fmt.Errorf("must be a string, path: %s, got value: %s",
							decoder.StackPointer(), valueToken.String()),
					)
				}
				var v string
				err = json.Unmarshal(valueToken, &v)
				if err != nil {
					return kerr.WrapTraceStack(
						fmt.Errorf("%w, path: %s, got value: %s",
							err, decoder.StackPointer(), valueToken.String()),
					)
				}
				cc.Path = v
			case "expires":
				if kind != '0' {
					return kerr.WrapTraceStack(
						fmt.Errorf("must be a number, path: %s, got value: %s",
							decoder.StackPointer(), valueToken.String()),
					)
				}
				var v float64
				err = json.Unmarshal(valueToken, &v)
				if err != nil {
					return kerr.WrapTraceStack(
						fmt.Errorf("%w, path: %s, got value: %s",
							err, decoder.StackPointer(), valueToken.String()),
					)
				}
				cc.RawExpires = valueToken.String()
				cc.Expires = time.Unix(int64(v), 0)
			}
		}
		if cc.Name == "" {
			continue
		}
		*c = append(*c, cc)
	}
	return nil
}

func (c *Cookies) Lookup(name string) *http.Cookie {
	for _, cookie := range *c {
		if cookie.Name == name {
			return cookie
		}
	}
	return nil
}

func (c *Cookies) CFClearance() *http.Cookie {
	return c.Lookup("cf_clearance")
}

type Headers http.Header

func (h *Headers) UnmarshalJSONFrom(decoder *jsontext.Decoder) error {
	token, err := decoder.ReadToken()
	if err != nil {
		return kerr.WrapTraceStack(err)
	}
	if token.Kind() != '{' {
		return kerr.WrapTraceStack(fmt.Errorf("headers must be object, path: %s", decoder.StackPointer()))
	}
	if *h == nil {
		*h = make(Headers)
	}
	for {
		keyToken, err := decoder.ReadToken()
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
		if keyToken.Kind() == '}' {
			break
		} else if keyToken.Kind() != '"' {
			return kerr.WrapTraceStack(
				fmt.Errorf("kind of exception, path: %s, got value: %s", decoder.StackPointer(), keyToken.String()),
			)
		}
		key := keyToken.String()
		value, err := decoder.ReadValue()
		if err != nil {
			return kerr.WrapTraceStack(err)
		}
		if value.Kind() == '"' {
			var v string
			err = json.Unmarshal(value, &v)
			if err != nil {
				return kerr.WrapTraceStack(
					fmt.Errorf("%w, path: %s, got value: %s", err, decoder.StackPointer(), value),
				)
			}
			(*http.Header)(h).Set(key, v)
		}
	}
	return nil
}

func (h *Headers) UserAgent() string {
	return (*http.Header)(h).Get("User-Agent")
}

func (h *Headers) SecCHUA() string {
	return (*http.Header)(h).Get("Sec-Ch-Ua")
}

type RespToken struct {
	Cookies Cookies `json:"cookies"`
	Headers Headers `json:"headers"`
}

func (c *Client) WafSession(ctx context.Context, i ReqToken) (*RespToken, error) {
	var url = (&url2.URL{Scheme: "http", Host: c.host, Path: "/wafSession"}).String()
	resp := c.httpClient.Post(url).SetBodyJsonMarshal(&i).Do(ctx)
	if resp.Err != nil {
		return nil, kerr.WrapTraceStack(resp.Err)
	}
	ret := &RespBase[RespToken]{}
	if err := unmarshalJson(resp, ret); err != nil {
		return nil, kerr.WrapTraceStack(err)
	}
	if ret.Code != 200 {
		return nil, kerr.WrapTraceStack(ret.ToError())
	}
	return &ret.Data, nil
}

func (c *Client) Health(ctx context.Context) error {
	var url = (&url2.URL{Scheme: "http", Host: c.host, Path: "/health"}).String()
	resp := c.httpClient.Get(url).Do(ctx)
	if resp.Err != nil {
		return kerr.WrapTraceStack(resp.Err)
	}
	ret := &RespBase[jsontext.Value]{}
	if err := unmarshalJson(resp, ret); err != nil {
		return kerr.WrapTraceStack(err)
	}
	if ret.Code != 200 {
		return kerr.WrapTraceStack(ret.ToError())
	}
	return nil
}
